package com.uxsino.simo.indicator.retractor;

import com.uxsino.commons.utils.config.ConfigProp;
import com.uxsino.simo.networkentity.EntityInfo;
import com.uxsino.simo.query.QueryContext;
import com.uxsino.simo.query.QueryTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;

/*
 * example: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE PerformanceMonitor SYSTEM
 * "/wasPerfTool/dtd/performancemonitor.dtd"> <PerformanceMonitor responseStatus="success" version=""> <Node
 * name="DESKTOP-ENVFHSONode01"> <Server name="server1" type="123"> <Stat name="Dynamic Caching" count="1"/> <Stat
 * name="Object" count="2"/> </Server> </Node> <Perf name="performance" /> <Node name="node2"> <Server name="server2"
 * type="789"> <Stat name="Dynamic Caching" count="3" /> <Stat name="Object" count="6" /> </Server> <Node> <Node
 * name="node3"> <Server name="server3" type="123"> <Stat name="Dynamic Caching" count="3" /> <Stat name="Object"
 * count="6" /> </Server> <Node> </PerformanceMonitor> To get Stat count data with server type="123": indicator="xxx"
 * parser="xml_list" prefix="PerformanceMonitor###Node###Server@@@type@@@123"> <column
 * col_id="Stat@@@name@@@Dynamic Caching!!!动态高速缓存###count" field="dyname_cache_data" /> <column
 * col_id="Stat@@@name@@@Object###count" field="object_count" />
 */

public class XMLRetractor extends ListValueRetractor {

    private static Logger logger = LoggerFactory.getLogger(XMLRetractor.class);

    @ConfigProp(name = "prefix")
    public String prefix;

    public XMLRetractor() {
        super();
        prefix = "";
    }

    @Override
    public Object doRetract(EntityInfo entity, QueryContext ctxt, QueryTemplate qt, Object obj) {
        if (obj == null) {
            logger.error("retract object is null");
            return null;
        }
        ArrayList<Map<String, Object>> result = new ArrayList<>();
        String xmlStr = removeTitle((String) obj);
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db;
            db = dbf.newDocumentBuilder();
            InputSource is = new InputSource();
            is.setCharacterStream(new StringReader(xmlStr));
            Document doc = db.parse(is);
            NodeList nl = doc.getChildNodes();
            List<Element> list = removePrefix(nl);

            for (Element element : list) {
                Map<String, Object> map = new HashMap<>();
                for (ColumnEntry entry : columnEntries) {
                    String keyName = entry.index;
                    Object joValue = getElementValue(element, keyName);
                    Object value = entry.retractor.retract(entity, ctxt, qt, joValue);
                    map.put(entry.field.getName(), value);
                }
                result.add(map);
            }

        } catch (SAXException | IOException | ParserConfigurationException e) {
            logger.error("error parsing object to xml document ", e);
            return null;
        }

        return result;
    }

    private String getElementValue(Element element, String key) {
        List<Element> list = new ArrayList<>();
        list.add(element);
        String[] strs = key.split("###");
        for (int i = 0; i < strs.length - 1; i++) {
            String[] filters = strs[i].split("@@@");
            List<Element> curList = getChildNodesByName(list, filters[0]);
            if (filters.length > 1) {
                curList = filterByAttribute(curList, filters[1], filters[2]);
            }
            list = curList;
        }
        if (list.size() != 1) {
            logger.error("extract target string error: list " + list.toString());
            return null;
        }
        String attr = strs[strs.length - 1];
        if (attr.equals(":::text")) {
            return list.get(0).getTextContent();
        } else {
            return list.get(0).getAttribute(strs[strs.length - 1]);
        }
    }

    private List<Element> removePrefix(NodeList nodeList) {
        List<Element> list = new ArrayList<>();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                list.add((Element) node);
            }
        }
        if (prefix.length() == 0) {
            return list;
        }
        String[] filters = prefix.split("###");
        for (int i = 0; i < filters.length; i++) {
            String[] strs = filters[i].split("@@@");
            List<Element> curList = null;
            if (!strs[0].equals("***")) {
                curList = getNodesByName(list, strs[0]);
            } else {
                curList = list;
            }
            if (strs.length > 1) {
                curList = filterByAttribute(curList, strs[1], strs[2]);
            }
            list = curList;
            if (i != filters.length - 1) {
                list = getChildNodes(curList);
            }
        }
        return list;
    }

    private List<Element> filterByAttribute(List<Element> list, String attrName, String attrValue) {
        List<Element> result = new ArrayList<>();
        List<String> attrValList = Arrays.asList(attrValue.split("!!!"));
        for (Element ele : list) {
            if (ele.hasAttribute(attrName) && attrValList.contains(ele.getAttribute(attrName))) {
                result.add(ele);
            }
        }
        return result;
    }

    // private void printElementsInfo(List<Element> list) {
    // for (Element ele : list) {
    // System.out.println("element: " + ele.getAttribute("name"));
    // }
    // }

    private List<Element> getNodesByName(NodeList nl, String name) {
        List<Element> result = new ArrayList<>();
        for (int i = 0; i < nl.getLength(); i++) {
            if (nl.item(i).getNodeName().equals(name) && nl.item(i).getNodeType() == Node.ELEMENT_NODE) {
                result.add((Element) nl.item(i));
            }
        }
        return result;
    }

    private List<Element> getNodesByName(List<Element> list, String name) {
        List<Element> result = new ArrayList<>();
        for (Element n : list) {
            if (n.getNodeName().equals(name)) {
                result.add(n);
            }
        }
        return result;
    }

    private List<Element> getChildNodesByName(List<Element> list, String name) {
        List<Element> result = new ArrayList<>();
        for (Element node : list) {
            NodeList children = node.getChildNodes();
            result.addAll(getNodesByName(children, name));
        }
        return result;
    }

    private List<Element> getChildNodes(List<Element> list) {
        List<Element> result = new ArrayList<>();
        for (Element node : list) {
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
                    result.add((Element) children.item(i));
                }
            }
        }
        return result;
    }

    // remove <!DOCTYPE *.dtd> line, the *.dtd cause error in parsing
    // remove first few <?xml> and <!-- --> lines
    // require <!-- content in one line
    private String removeTitle(String input) {
        String[] strs = input.trim().split("\n");
        StringBuilder sb = new StringBuilder();
        int index = 0;
        List<Integer> indexsRemove = new ArrayList<Integer>();
        while (index < strs.length) {
            String s = strs[index].trim();
            if (s.length() != 0 && (s.startsWith("<!DOCTYPE") || s.startsWith("<?xml") || s.startsWith("<!--"))) {
                indexsRemove.add(index);
            }
            index++;
        }
        index = 0;
        while (index < strs.length) {
            if (!indexsRemove.contains(index)) {
                sb.append(strs[index]);
            }
            index++;
        }
        return sb.toString();
    }

    /* private String removeTitle(String input) {
        String[] strs = input.trim().split("\n");
        StringBuilder sb = new StringBuilder();
        int index = 0;
        while (index < strs.length) {
            String s = strs[index].trim();
            if (s.length() != 0 && !(s.startsWith("<!DOCTYPE") || s.startsWith("<?xml") || s.startsWith("<!--"))) {
                break;
            }
            index++;
        }
        while (index < strs.length) {
            sb.append(strs[index]);
            index++;
        }
        return sb.toString();
    }*/

}
